//! # TLS/SSL Configuration and Utilities
//!
//! This module provides TLS/SSL configuration and helper functions for secure
//! communication between node server and agents. It supports:
//! - Certificate loading and validation
//! - Client and server TLS configuration
//! - Custom CA certificate support
//! - Development mode with hostname verification disabled
//!
//! ## Usage
//!
//! ```rust,ignore
//! use shared::tls::TlsConfig;
//!
//! // Server configuration with certificates
//! let config = TlsConfig::with_certs(
//!     "./certs/server.crt",
//!     "./certs/server.key",
//!     Some("./certs/ca.crt")
//! );
//!
//! // Client configuration with CA only
//! let config = TlsConfig::client_only("./certs/ca.crt");
//! ```

use anyhow::Result;
use rustls::{ClientConfig, ServerConfig};
use rustls_pemfile::{certs, private_key};
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::sync::Arc;

/// TLS configuration structure
///
/// Contains all necessary paths and settings for TLS/SSL connections.
/// Supports both server and client configurations.
#[derive(Debug, Clone)]
pub struct TlsConfig {
    /// Whether TLS is enabled
    pub enabled: bool,
    /// Path to server certificate file (PEM format)
    pub cert_path: Option<String>,
    /// Path to private key file (PEM format)
    pub key_path: Option<String>,
    /// Path to CA certificate file (PEM format)
    pub ca_path: Option<String>,
    /// Whether to verify server hostname (disable for development)
    pub verify_hostname: bool,
}

impl Default for TlsConfig {
    fn default() -> Self {
        Self::new()
    }
}

impl TlsConfig {
    /// Create a new disabled TLS configuration
    pub fn new() -> Self {
        Self {
            enabled: false,
            cert_path: None,
            key_path: None,
            ca_path: None,
            verify_hostname: true,
        }
    }

    /// Create TLS configuration with certificate files
    ///
    /// # Arguments
    /// * `cert_path` - Path to server certificate file
    /// * `key_path` - Path to private key file
    /// * `ca_path` - Optional path to CA certificate file
    pub fn with_certs(
        cert_path: impl Into<String>,
        key_path: impl Into<String>,
        ca_path: Option<impl Into<String>>,
    ) -> Self {
        Self {
            enabled: true,
            cert_path: Some(cert_path.into()),
            key_path: Some(key_path.into()),
            ca_path: ca_path.map(|p| p.into()),
            verify_hostname: true,
        }
    }

    /// Disable hostname verification (for development only)
    ///
    /// **Warning**: This should never be used in production environments
    /// as it makes the connection vulnerable to man-in-the-middle attacks.
    pub fn disable_hostname_verification(mut self) -> Self {
        self.verify_hostname = false;
        self
    }

    /// Create client-only TLS configuration (no server certificates needed)
    ///
    /// This is suitable for agents that only connect as clients to RabbitMQ.
    /// Hostname verification is disabled by default for flexibility.
    ///
    /// # Arguments
    /// * `ca_path` - Path to CA certificate file
    pub fn client_only(ca_path: impl Into<String>) -> Self {
        Self {
            enabled: true,
            cert_path: None,
            key_path: None,
            ca_path: Some(ca_path.into()),
            verify_hostname: false, // Disabled for RabbitMQ client connections
        }
    }
}

/// Create server-side TLS configuration
///
/// Loads server certificate and private key to enable TLS connections
/// from clients. Returns None if TLS is disabled.
///
/// # Arguments
/// * `tls_config` - TLS configuration with certificate paths
///
/// # Returns
/// * `Ok(Some(config))` - Successfully created server TLS configuration
/// * `Ok(None)` - TLS is disabled
/// * `Err(...)` - Failed to load certificates or create configuration
pub fn create_server_config(tls_config: &TlsConfig) -> Result<Option<Arc<ServerConfig>>> {
    if !tls_config.enabled {
        return Ok(None);
    }

    let cert_path = tls_config
        .cert_path
        .as_ref()
        .ok_or_else(|| anyhow::anyhow!("Certificate path not specified"))?;
    let key_path = tls_config
        .key_path
        .as_ref()
        .ok_or_else(|| anyhow::anyhow!("Private key path not specified"))?;

    // Load certificate file
    let cert_file = File::open(cert_path)?;
    let mut cert_reader = BufReader::new(cert_file);
    let cert_chain: Vec<rustls::pki_types::CertificateDer> =
        certs(&mut cert_reader).collect::<Result<Vec<_>, _>>()?;

    if cert_chain.is_empty() {
        return Err(anyhow::anyhow!("No certificates found in {}", cert_path));
    }

    // Read private key file
    let key_file = File::open(key_path)?;
    let mut key_reader = BufReader::new(key_file);
    let private_key = private_key(&mut key_reader)?
        .ok_or_else(|| anyhow::anyhow!("No private key found in {}", key_path))?;

    // Create server configuration
    let config = ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(cert_chain, private_key)?;

    Ok(Some(Arc::new(config)))
}

/// Creates a TLS configuration for client connections with custom CA and hostname verification options.
/// This function supports both custom CA certificates and system default certificates.
pub fn create_client_config(tls_config: &TlsConfig) -> Result<Option<Arc<ClientConfig>>> {
    if !tls_config.enabled {
        return Ok(None);
    }

    let mut root_store = rustls::RootCertStore::empty();

    // If custom CA certificate is provided, use it
    if let Some(ca_path) = &tls_config.ca_path {
        let ca_file = File::open(ca_path)?;
        let mut ca_reader = BufReader::new(ca_file);
        let ca_certs: Vec<rustls::pki_types::CertificateDer> =
            certs(&mut ca_reader).collect::<Result<Vec<_>, _>>()?;

        for cert in ca_certs {
            root_store.add(cert)?;
        }
    } else {
        // Use system default CA certificates
        root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
    }

    let config_builder = ClientConfig::builder().with_root_certificates(root_store);

    // If hostname verification is disabled, use dangerous configuration (development only)
    let config = if !tls_config.verify_hostname {
        #[derive(Debug)]
        struct NoCertificateVerification;

        impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
            fn verify_server_cert(
                &self,
                _end_entity: &rustls::pki_types::CertificateDer<'_>,
                _intermediates: &[rustls::pki_types::CertificateDer<'_>],
                _server_name: &rustls::pki_types::ServerName<'_>,
                _ocsp_response: &[u8],
                _now: rustls::pki_types::UnixTime,
            ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
                Ok(rustls::client::danger::ServerCertVerified::assertion())
            }

            fn verify_tls12_signature(
                &self,
                _message: &[u8],
                _cert: &rustls::pki_types::CertificateDer<'_>,
                _dss: &rustls::DigitallySignedStruct,
            ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
            {
                Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
            }

            fn verify_tls13_signature(
                &self,
                _message: &[u8],
                _cert: &rustls::pki_types::CertificateDer<'_>,
                _dss: &rustls::DigitallySignedStruct,
            ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
            {
                Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
            }

            fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
                vec![
                    rustls::SignatureScheme::RSA_PKCS1_SHA1,
                    rustls::SignatureScheme::ECDSA_SHA1_Legacy,
                    rustls::SignatureScheme::RSA_PKCS1_SHA256,
                    rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
                    rustls::SignatureScheme::RSA_PKCS1_SHA384,
                    rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
                    rustls::SignatureScheme::RSA_PKCS1_SHA512,
                    rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
                    rustls::SignatureScheme::RSA_PSS_SHA256,
                    rustls::SignatureScheme::RSA_PSS_SHA384,
                    rustls::SignatureScheme::RSA_PSS_SHA512,
                    rustls::SignatureScheme::ED25519,
                    rustls::SignatureScheme::ED448,
                ]
            }
        }

        ClientConfig::builder()
            .dangerous()
            .with_custom_certificate_verifier(Arc::new(NoCertificateVerification))
            .with_no_client_auth()
    } else {
        config_builder.with_no_client_auth()
    };

    Ok(Some(Arc::new(config)))
}

/// Validate that certificate files exist and are readable
///
/// Verifies that all configured certificate files exist on the filesystem
/// before attempting to use them in TLS configuration.
pub fn validate_cert_files(tls_config: &TlsConfig) -> Result<()> {
    if !tls_config.enabled {
        return Ok(());
    }

    if let Some(cert_path) = &tls_config.cert_path {
        if !Path::new(cert_path).exists() {
            return Err(anyhow::anyhow!("Certificate file not found: {}", cert_path));
        }
    }

    if let Some(key_path) = &tls_config.key_path {
        if !Path::new(key_path).exists() {
            return Err(anyhow::anyhow!("Private key file not found: {}", key_path));
        }
    }

    if let Some(ca_path) = &tls_config.ca_path {
        if !Path::new(ca_path).exists() {
            return Err(anyhow::anyhow!(
                "CA certificate file not found: {}",
                ca_path
            ));
        }
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    //! Unit tests for the TLS module

    use super::*;
    use tempfile::NamedTempFile;

    #[test]
    fn tls_config_new() {
        let config = TlsConfig::new();

        assert!(!config.enabled);
        assert!(config.cert_path.is_none());
        assert!(config.key_path.is_none());
        assert!(config.ca_path.is_none());
        assert!(config.verify_hostname);
    }

    #[test]
    fn tls_config_with_certs() {
        let cert_path = "./server.crt";
        let key_path = "./server.key";
        let ca_path = "./ca.crt";

        let config = TlsConfig::with_certs(cert_path, key_path, Some(ca_path));

        assert!(config.enabled);
        assert_eq!(config.cert_path, Some(cert_path.to_string()));
        assert_eq!(config.key_path, Some(key_path.to_string()));
        assert_eq!(config.ca_path, Some(ca_path.to_string()));
        assert!(config.verify_hostname);
    }

    #[test]
    fn tls_config_with_certs_no_ca() {
        let cert_path = "./server.crt";
        let key_path = "./server.key";

        let config = TlsConfig::with_certs(cert_path, key_path, None::<String>);

        assert!(config.enabled);
        assert_eq!(config.cert_path, Some(cert_path.to_string()));
        assert_eq!(config.key_path, Some(key_path.to_string()));
        assert!(config.ca_path.is_none());
    }

    #[test]
    fn disable_hostname_verification() {
        let config = TlsConfig::with_certs("cert.pem", "key.pem", Some("ca.pem"))
            .disable_hostname_verification();

        assert!(config.enabled);
        assert!(!config.verify_hostname);
    }

    #[test]
    fn client_only_config() {
        let ca_path = "./ca.crt";
        let config = TlsConfig::client_only(ca_path);

        assert!(config.enabled);
        assert!(config.cert_path.is_none());
        assert!(config.key_path.is_none());
        assert_eq!(config.ca_path, Some(ca_path.to_string()));
        assert!(!config.verify_hostname); // Should be disabled by default for client-only
    }

    #[test]
    fn validate_cert_files_missing_cert() {
        let config = TlsConfig::with_certs("nonexistent.crt", "key.pem", None::<String>);
        assert!(validate_cert_files(&config).is_err());
    }

    #[test]
    fn validate_cert_files_missing_key() {
        // Create a temporary cert file
        let cert_file = NamedTempFile::new().unwrap();
        let cert_path = cert_file.path().to_str().unwrap();

        let config = TlsConfig::with_certs(cert_path, "nonexistent.key", None::<String>);
        assert!(validate_cert_files(&config).is_err());
    }

    #[test]
    fn validate_cert_files_missing_ca() {
        // Create temporary cert and key files
        let cert_file = NamedTempFile::new().unwrap();
        let key_file = NamedTempFile::new().unwrap();

        let cert_path = cert_file.path().to_str().unwrap();
        let key_path = key_file.path().to_str().unwrap();

        let config = TlsConfig::with_certs(cert_path, key_path, Some("nonexistent_ca.crt"));
        assert!(validate_cert_files(&config).is_err());
    }

    #[test]
    fn validate_cert_files_all_exist() {
        // Create temporary files
        let cert_file = NamedTempFile::new().unwrap();
        let key_file = NamedTempFile::new().unwrap();
        let ca_file = NamedTempFile::new().unwrap();

        let cert_path = cert_file.path().to_str().unwrap();
        let key_path = key_file.path().to_str().unwrap();
        let ca_path = ca_file.path().to_str().unwrap();

        let config = TlsConfig::with_certs(cert_path, key_path, Some(ca_path));
        assert!(validate_cert_files(&config).is_ok());
    }

    #[test]
    fn config_builder_patterns() {
        // Test method chaining
        let config = TlsConfig::with_certs("cert.pem", "key.pem", Some("ca.pem"))
            .disable_hostname_verification();

        assert!(config.enabled);
        assert!(!config.verify_hostname);

        // Test client-only with modifications
        let client_config = TlsConfig::client_only("ca.pem");
        assert!(client_config.enabled);
        assert!(!client_config.verify_hostname); // Default for client-only
    }

    #[test]
    fn real_certificate_files() {
        // Test with actual certificate files from the project
        let cert_path = "./test_data/server.crt";
        let key_path = "./test_data/server.key";
        let ca_path = "./test_data/ca.crt";

        // Only run this test if the cert files actually exist
        if Path::new(cert_path).exists()
            && Path::new(key_path).exists()
            && Path::new(ca_path).exists()
        {
            let config = TlsConfig::with_certs(cert_path, key_path, Some(ca_path));
            assert!(validate_cert_files(&config).is_ok());
        }
    }

    #[test]
    fn empty_paths() {
        let config = TlsConfig::with_certs("", "", Some(""));
        assert!(validate_cert_files(&config).is_err());
    }
}
